Skip to content

脚本文件 v2

生成单个模块增量代码识别 jacoco 的文件的覆盖率报告文件

#!/usr/bin/env bash  
set -euo pipefail  
  
# ===== 合并版本:虚拟环境 + diff-cover 分析脚本 =====# 特点:单文件,用完即抛,不修改工程代码  
  
# ===== 配置参数 =====MODULES=${MODULES:-"soms-service-start"}                     # 多个模块用逗号分隔,为空时自动检测  
THRESHOLD=${THRESHOLD:-100}                # 覆盖率阈值  
OUTPUT_DIR=${OUTPUT_DIR:-"./reports"} # 报告输出目录  
SKIP_TESTS=${SKIP_TESTS:-0}               # 跳过测试执行检查,使用现有报告: 1=跳过  
FORCE_ANALYSIS=${FORCE_ANALYSIS:-0}       # 强制分析: 1=强制  
BASE_BRANCH=${BASE_BRANCH:-""}            # 基准分支  
SKIP_BRANCH_CHECK=${SKIP_BRANCH_CHECK:-0} # 跳过远程分支比对: 1=跳过  
  
# 虚拟环境相关配置  
VENV_DIR=${VENV_DIR:-"./temp_venv"}           # 临时虚拟环境目录  
CLEANUP_VENV=${CLEANUP_VENV:-1}               # 执行后是否清理虚拟环境: 1=清理  
  
# ===== 颜色定义 =====RED='\033[0;31m'  
GREEN='\033[0;32m'  
YELLOW='\033[1;33m'  
BLUE='\033[0;34m'  
NC='\033[0m' # No Color  
  
# ===== 日志函数 =====log_info() {  
    echo -e "${BLUE}ℹ️  $1${NC}" >&2  
}  
  
log_success() {  
    echo -e "${GREEN}✅ $1${NC}" >&2  
}  
  
log_warning() {  
    echo -e "${YELLOW}⚠️  $1${NC}" >&2  
}  
  
log_error() {  
    echo -e "${RED}❌ $1${NC}" >&2  
}  
  
# ===== 检查环境依赖 =====check_environment() {  
    log_info "检查环境依赖..."  
    # 检查必要工具  
    for tool in git python3 bc; do  
        if ! command -v "$tool" &> /dev/null; then  
            log_error "$tool 未安装或不在PATH中"  
            log_info "请安装 $tool 后重试"  
            exit 1  
        fi  
    done    log_success "环境检查通过"  
}  
  
# ===== 虚拟环境管理 =====create_temp_venv() {  
    if [-d "$VENV_DIR"](); then  
        if ["$CLEANUP_VENV" == "1"](); then  
            log_info "发现已存在的虚拟环境,清理后重建: $VENV_DIR"  
            rm -rf "$VENV_DIR"  
        else  
            log_info "发现已存在的虚拟环境,复用现有环境: $VENV_DIR"  
            # 检查现有环境是否可用  
            if [-f "$VENV_DIR/bin/activate"](); then  
                log_success "复用现有虚拟环境"  
                return 0  
            else  
                log_warning "现有虚拟环境不完整,重建..."  
                rm -rf "$VENV_DIR"  
            fi  
        fi    fi    log_info "创建临时虚拟环境: $VENV_DIR"  
    # 创建虚拟环境  
    if python3 -m venv "$VENV_DIR" >/dev/null 2>&1; then  
        log_success "虚拟环境创建成功"  
    else  
        log_error "虚拟环境创建失败"  
        exit 1  
    fi  
}  
  
setup_venv() {  
    log_info "激活虚拟环境并安装依赖..."  
    # 激活虚拟环境  
    source "$VENV_DIR/bin/activate"  
    log_success "虚拟环境已激活"  
    # 检查diff-cover是否已安装  
    if python -c "import diff_cover" 2>/dev/null; then  
        log_success "diff-cover已安装,跳过安装步骤"  
        return 0  
    fi  
    # 升级pip  
    log_info "升级pip..."  
    pip install --upgrade pip >/dev/null 2>&1  
    # 安装diff-cover  
    log_info "安装diff-cover..."  
    if pip install diff-cover >/dev/null 2>&1; then  
        log_success "diff-cover安装完成"  
    else  
        log_error "diff-cover安装失败"  
        exit 1  
    fi  
    # 验证安装  
    if python -c "import diff_cover" 2>/dev/null; then  
        log_success "依赖验证通过"  
    else  
        log_error "依赖验证失败"  
        exit 1  
    fi  
}  
  
cleanup_venv() {  
    if ["$CLEANUP_VENV" == "1" && -d "$VENV_DIR"](); then  
        log_info "清理临时虚拟环境: $VENV_DIR"  
        rm -rf "$VENV_DIR"  
        log_success "临时虚拟环境已清理"  
    fi  
}  
  
# ===== 验证分支是否存在 =====validate_branch() {  
    local branch="$1"  
    local branch_type="$2"  
    if git show-ref --verify --quiet "refs/remotes/$branch" 2>/dev/null; then  
        log_success "$branch_type 验证通过: $branch"  
        return 0  
    else  
        log_error "$branch_type $branch 不存在"  
        log_info "可用的分支:"  
        git branch -a 2>/dev/null | head -10 | while read -r line; do  
            log_info "  $line"  
        done  
        log_info ""  
        log_info "解决方案:"  
        log_info "  1. 手动指定远程分支: BASE_BRANCH=origin/your-branch ./diff_cover_venv.sh"  
        log_info "  2. 拉取远程分支: git fetch origin"  
        log_info "  3. 检查远程分支是否存在: git branch -r"  
        exit 1  
    fi  
}  
  
# ===== 自动检测远程分支 =====detect_base_branch() {  
    # 如果用户已经指定了基准分支,直接使用  
    if [-n "$BASE_BRANCH"](); then  
        log_info "使用用户指定的基准分支: $BASE_BRANCH"  
        validate_branch "$BASE_BRANCH" "基准分支"  
        return 0  
    fi  
    # 如果设置了跳过远程分支比对,使用当前分支  
    if ["$SKIP_BRANCH_CHECK" == "1"](); then  
        log_info "跳过远程分支比对,使用当前分支作为基准"  
        BASE_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")  
        log_success "使用当前分支作为基准: $BASE_BRANCH"  
        return 0  
    fi  
    log_info "自动检测远程基准分支..."  
    # 检查是否有远程仓库配置  
    if ! git remote get-url origin >/dev/null 2>&1; then  
        log_error "未配置远程仓库 origin,请检查 Git 配置"  
        log_info "解决方案:"  
        log_info "  1. 检查是否在 Git 仓库中: git status"  
        log_info "  2. 添加远程仓库: git remote add origin <仓库URL>"  
        exit 1  
    fi  
    # 只从远程获取默认分支,不允许降级到本地分支  
    local default_branch=""  
    if ! git fetch origin >/dev/null 2>&1; then  
        log_error "无法连接到远程仓库 origin"        log_info "解决方案:"  
        log_info "  1. 检查网络连接"  
        log_info "  2. 检查远程仓库配置: git remote -v"  
        log_info "  3. 手动指定远程分支: BASE_BRANCH=origin/main ./diff_cover_venv.sh"  
        exit 1  
    fi  
    # 尝试多种方式获取远程默认分支  
    default_branch=$(git remote show origin | sed -n '/HEAD branch/s/.*: //p' 2>/dev/null)  
    # 如果上面失败,尝试从远程分支列表获取  
    if [-z "$default_branch"](); then  
        default_branch=$(git ls-remote --symref origin HEAD | sed -n 's/.*refs\/heads\/\([^[:space:]]*\).*/\1/p' 2>/dev/null)  
    fi  
    # 如果还是失败,尝试常见的默认分支名  
    if [-z "$default_branch"](); then  
        for branch in main master develop; do  
            if git ls-remote --heads origin "$branch" | grep -q "$branch"; then  
                default_branch="$branch"  
                log_info "通过远程分支检测到默认分支: $branch"  
                break  
            fi  
        done    fi    # 如果仍然找不到远程分支,报错  
    if [-z "$default_branch"](); then  
        log_error "无法检测到远程默认分支"  
        log_info "可用的远程分支:"  
        git ls-remote --heads origin 2>/dev/null | sed 's/.*refs\/heads\///' | head -10 | while read -r branch; do  
            log_info "  origin/$branch"  
        done  
        log_info ""  
        log_info "解决方案:"  
        log_info "  1. 手动指定远程分支: BASE_BRANCH=origin/main ./diff_cover_venv.sh"  
        log_info "  2. 检查远程仓库是否有默认分支"  
        exit 1  
    fi  
    # 设置基准分支 - 只使用远程分支  
    if ["$default_branch" == *"origin/"*](); then  
        BASE_BRANCH="$default_branch"  
    else  
        # 只使用远程分支,不允许降级到本地分支  
        BASE_BRANCH="origin/$default_branch"  
    fi  
    log_success "检测到基准分支: $BASE_BRANCH"  
    # 最终验证远程分支是否存在  
    validate_branch "$BASE_BRANCH" "基准分支"  
}  
  
# ===== 检查分支完整性 =====check_branch_integrity() {  
    # 如果设置了跳过远程分支比对,也跳过分支完整性检查  
    if ["$SKIP_BRANCH_CHECK" == "1"](); then  
        log_info "跳过分支完整性检查"  
        return 0  
    fi  
    log_info "检查当前分支是否包含基准分支的所有提交..."  
    # 获取当前分支名  
    local current_branch  
    current_branch=$(git rev-parse --abbrev-ref HEAD)  
    # 检查当前分支是否包含基准分支的所有提交  
    if git merge-base --is-ancestor "$BASE_BRANCH" "$current_branch" 2>/dev/null; then  
        log_success "当前分支 $current_branch 包含基准分支 $BASE_BRANCH 的所有提交"  
    else  
        log_warning "当前分支 $current_branch 不包含基准分支 $BASE_BRANCH 的所有提交"  
        log_warning "这可能导致增量覆盖率分析不准确"  
        # 显示缺失的提交数量  
        local missing_commits  
        missing_commits=$(git rev-list --count "$BASE_BRANCH" ^"$current_branch" 2>/dev/null || echo "未知")  
        log_warning "基准分支领先当前分支 $missing_commits 个提交"  
        # 询问是否继续  
        if ["${FORCE_ANALYSIS:-0}" != "1"](); then  
            log_warning "建议先合并基准分支或使用 --force 参数强制分析"  
            log_warning "设置环境变量 FORCE_ANALYSIS=1 可以跳过此检查"  
            exit 1  
        else  
            log_warning "已设置 FORCE_ANALYSIS=1,继续分析..."  
        fi  
    fi}  
  
# ===== 自动检测模块 =====detect_modules() {  
    if [-n "$MODULES"](); then  
        log_info "使用指定的模块: $MODULES"  
        return 0  
    fi  
    log_info "自动检测项目模块..."  
    # 检测 Gradle 项目模块  
    if [-f "settings.gradle"](); then  
        local gradle_modules  
        gradle_modules=$(grep "include " settings.gradle | sed "s/.*include ['\"]//g" | sed "s/['\"].*//g" | tr '\n' ',' | sed 's/,$//')  
          
        if [-n "$gradle_modules"](); then  
            MODULES="$gradle_modules"  
            log_success "检测到 Gradle 模块: $MODULES"  
            return 0  
        fi  
    fi    # 检测 Maven 项目模块  
    if [-f "pom.xml"](); then  
        local maven_modules  
        maven_modules=$(grep -E "<module>" pom.xml | sed "s/.*<module>//g" | sed "s/<\/module>.*//g" | tr '\n' ',' | sed 's/,$//')  
          
        if [-n "$maven_modules"](); then  
            MODULES="$maven_modules"  
            log_success "检测到 Maven 模块: $MODULES"  
            return 0  
        fi  
    fi    # 默认模块检测(基于目录结构)  
    local detected_modules=""  
    for dir in */; do  
        if [-d "${dir}src/main/java"]() || [-d "${dir}src/main/kotlin"](); then  
            local module_name="${dir%/}"  
            if [-z "$detected_modules"](); then  
                detected_modules="$module_name"  
            else  
                detected_modules="$detected_modules,$module_name"  
            fi  
        fi    done    if [-n "$detected_modules"](); then  
        MODULES="$detected_modules"  
        log_success "检测到模块: $MODULES"  
    else  
        log_error "未检测到模块,请手动指定 MODULES 环境变量"  
        exit 1  
    fi  
}  
  
# ===== 检查覆盖率报告 =====check_coverage_report() {  
    local module="$1"  
    if [$SKIP_TESTS -eq 1](); then  
        log_info "[$module] 跳过测试执行检查,直接使用现有报告..."  
    else  
        log_info "[$module] 检查覆盖率报告..."  
        log_warning "请确保已运行测试并生成了 Jacoco 报告"  
        log_warning "例如: mvn test jacoco:report -pl $module"  
    fi  
    local coverage_xml="$module/target/jacoco/jacoco.xml"  
    if [ ! -f "$coverage_xml" ]; then  
        log_error "未找到 Jacoco 报告: $coverage_xml"  
        log_error "请先运行测试生成覆盖率报告"  
        return 1  
    fi  
    log_success "找到 Jacoco 报告: $coverage_xml"  
    echo "$coverage_xml"  
}  
  
# ===== 转换Jacoco XML为Cobertura XML =====  
convert_jacoco_to_cobertura() {  
    local jacoco_xml="$1"  
    local cobertura_xml="$2"  
    log_info "转换 Jacoco XML 为 Cobertura XML 格式..."  
    # 创建临时转换脚本  
    local script_file="/tmp/jacoco_to_cobertura_$$.py"  
    cat > "$script_file" << 'EOF'  
#!/usr/bin/env python3  
import xml.etree.ElementTree as ET  
import sys  
import os  
import time  
  
def convert_jacoco_to_cobertura(jacoco_file, cobertura_file):  
    try:        tree = ET.parse(jacoco_file)        root = tree.getroot()                coverage = ET.Element('coverage')  
        coverage.set('line-rate', '0.0')        coverage.set('branch-rate', '0.0')        coverage.set('lines-covered', '0')        coverage.set('lines-valid', '0')        coverage.set('branches-covered', '0')        coverage.set('branches-valid', '0')        coverage.set('complexity', '0.0')        coverage.set('version', '1.9')        coverage.set('timestamp', str(int(time.time())))                sources = ET.SubElement(coverage, 'sources')  
        source = ET.SubElement(sources, 'source')        # 动态获取模块名和源码路径  
        jacoco_dir = os.path.dirname(jacoco_file)        # 从 jacoco.xml 路径推导模块路径: hinton-application/target/jacoco/jacoco.xml -> hinton-application  
        module_name = os.path.basename(os.path.dirname(os.path.dirname(jacoco_dir)))        source.text = os.path.join(os.getcwd(), module_name, 'src', 'main', 'java')                packages = ET.SubElement(coverage, 'packages')  
        total_lines_covered = 0        total_lines_valid = 0                for package in root.findall('.//package'):  
            pkg_name = package.get('name', '')            pkg_lines_covered = 0            pkg_lines_valid = 0                        pkg_elem = ET.SubElement(packages, 'package')  
            pkg_elem.set('name', pkg_name)            pkg_elem.set('line-rate', '0.0')            pkg_elem.set('branch-rate', '0.0')            pkg_elem.set('complexity', '0.0')                        classes = ET.SubElement(pkg_elem, 'classes')  
                        for sourcefile in package.findall('.//sourcefile'):  
                class_name = sourcefile.get('name', '').replace('.java', '')                class_lines_covered = 0                class_lines_valid = 0                                class_elem = ET.SubElement(classes, 'class')  
                class_elem.set('name', class_name)                class_elem.set('filename', f"{pkg_name.replace('.', '/')}/{sourcefile.get('name', '')}")                class_elem.set('line-rate', '0.0')                class_elem.set('branch-rate', '0.0')                class_elem.set('complexity', '0.0')                                lines = ET.SubElement(class_elem, 'lines')  
                                for line in sourcefile.findall('.//line'):  
                    line_num = line.get('nr', '0')                    line_hits = int(line.get('ci', '0'))                                        line_elem = ET.SubElement(lines, 'line')  
                    line_elem.set('number', line_num)                    line_elem.set('hits', str(line_hits))                    line_elem.set('branch', 'false')                                        class_lines_valid += 1  
                    if line_hits > 0:                        class_lines_covered += 1                                if class_lines_valid > 0:  
                    class_rate = class_lines_covered / class_lines_valid                    class_elem.set('line-rate', f"{class_rate:.4f}")                                pkg_lines_covered += class_lines_covered  
                pkg_lines_valid += class_lines_valid                        if pkg_lines_valid > 0:  
                pkg_rate = pkg_lines_covered / pkg_lines_valid                pkg_elem.set('line-rate', f"{pkg_rate:.4f}")                        total_lines_covered += pkg_lines_covered  
            total_lines_valid += pkg_lines_valid                if total_lines_valid > 0:  
            total_rate = total_lines_covered / total_lines_valid            coverage.set('line-rate', f"{total_rate:.4f}")            coverage.set('lines-covered', str(total_lines_covered))            coverage.set('lines-valid', str(total_lines_valid))                tree = ET.ElementTree(coverage)  
        ET.indent(tree, space="  ", level=0)        tree.write(cobertura_file, encoding='utf-8', xml_declaration=True)        return True            except Exception as e:  
        print(f"转换失败: {e}", file=sys.stderr)  
        return False  
if __name__ == "__main__":  
    if len(sys.argv) != 3:        print("用法: python3 jacoco_to_cobertura.py <jacoco.xml> <cobertura.xml>", file=sys.stderr)  
        sys.exit(1)        if convert_jacoco_to_cobertura(sys.argv[1], sys.argv[2]):  
        print(f"转换成功: {sys.argv[2]}")  
        sys.exit(0)    else:        sys.exit(1)EOF  
    # 执行转换  
    if python "$script_file" "$jacoco_xml" "$cobertura_xml"; then  
        log_success "转换完成: $cobertura_xml"  
        rm -f "$script_file"  
        return 0  
    else  
        log_error "转换失败"  
        rm -f "$script_file"  
        return 1  
    fi  
}  
  
# ===== 使用diff-cover分析增量覆盖率 =====analyze_diff_coverage() {  
    local module="$1"  
    local cobertura_xml="$2"  
    log_info "[$module] 使用 diff-cover 分析增量覆盖率..."  
    # 创建报告目录(如果不存在)  
    [! -d "$OUTPUT_DIR"]() && mkdir -p "$OUTPUT_DIR"  
    local html_report="$OUTPUT_DIR/${module}-diff-cover-report.html"  
    local json_report="$OUTPUT_DIR/${module}-diff-cover-report.json"  
    # 执行diff-cover,指定源码根目录以正确匹配路径  
    # 添加更多报告选项以增强覆盖情况展示  
    local diff_output  
    local diff_cmd="diff-cover \"$cobertura_xml\""  
    # 如果设置了跳过远程分支比对,不进行分支比对  
    if ["$SKIP_BRANCH_CHECK" == "1"](); then  
        log_info "[$module] 跳过分支比对,进行整体覆盖率分析"  
        diff_cmd="$diff_cmd --src-roots \"$module/src/main/java\""  
    else  
        diff_cmd="$diff_cmd --compare-branch \"$BASE_BRANCH\" --src-roots \"$module/src/main/java\""  
    fi  
    # 添加调试信息  
    log_info "[$module] 执行命令: $diff_cmd"  
    log_info "[$module] HTML报告路径: $html_report"  
    log_info "[$module] JSON报告路径: $json_report"  
    if diff_output=$(eval "$diff_cmd \        --html-report \"$html_report\" \  
        --json-report \"$json_report\" \  
        --markdown-report \"$OUTPUT_DIR/${module}-diff-cover-report.md\" \  
        --show-uncovered \        --fail-under 0" 2>&1); then  
        log_success "[$module] diff-cover 分析完成"  
        log_info "报告文件:"  
        # 检查报告文件是否真的被创建  
        if [-f "$html_report"](); then  
            log_info "  - HTML: $html_report ✅"  
        else  
            log_warning "  - HTML: $html_report ❌ (文件未创建)"  
        fi  
        if [-f "$json_report"](); then  
            log_info "  - JSON: $json_report ✅"  
        else  
            log_warning "  - JSON: $json_report ❌ (文件未创建)"  
        fi  
        if [-f "$OUTPUT_DIR/${module}-diff-cover-report.md"](); then  
            log_info "  - Markdown: $OUTPUT_DIR/${module}-diff-cover-report.md ✅"  
        else  
            log_warning "  - Markdown: $OUTPUT_DIR/${module}-diff-cover-report.md ❌ (文件未创建)"  
        fi  
        # 显示详细的覆盖率信息  
        echo ""  
        echo "========================================"  
        echo "📊 [$module] 增量覆盖率详情"  
        echo "========================================"  
        # 提取并显示覆盖率摘要信息  
        local coverage_summary=$(echo "$diff_output" | sed -n '/^Total:/,/^Coverage:/p')  
        if [-n "$coverage_summary"](); then  
            echo "$coverage_summary"  
        fi  
        # 显示文件级别的覆盖率信息  
        local file_coverage=$(echo "$diff_output" | grep -E "\.java \([0-9.]+%\):" | head -10)  
        if [-n "$file_coverage"](); then  
            echo ""  
            echo "📁 文件覆盖率详情:"  
            echo "$file_coverage" | while read -r line; do  
                echo "  $line"  
            done  
            # 如果文件超过10个,显示省略信息  
            local total_files=$(echo "$diff_output" | grep -c "\.java \([0-9.]+%\):")  
            if [$total_files -gt 10](); then  
                echo "  ... 还有 $((total_files - 10)) 个文件"  
            fi  
        fi        echo "========================================"  
        echo ""  
        # 从控制台输出中解析覆盖率  
        local coverage_percent=$(echo "$diff_output" | grep "Coverage:" | sed 's/.*Coverage: \([0-9.]*\)%.*/\1/')  
        local total_lines=$(echo "$diff_output" | grep "Total:" | sed 's/.*Total: *\([0-9]*\) lines.*/\1/')  
        local missing_lines=$(echo "$diff_output" | grep "Missing:" | sed 's/.*Missing: *\([0-9]*\) lines.*/\1/')  
          
        # 调试信息(可选,用于排查问题)  
        # log_info "[$module] 调试 - 原始输出片段:"  
        # echo "$diff_output" | grep -E "(Total:|Missing:|Coverage:)" | head -3 | while read -r line; do        #     log_info "[$module] 调试 - $line"        # done        if [-z "$coverage_percent"](); then  
            # 如果没有找到覆盖率信息,检查是否有"No lines with coverage information"  
            if echo "$diff_output" | grep -q "No lines with coverage information"; then  
                log_info "[$module] 没有检测到需要覆盖的代码行"  
                COVERAGE_INFO="0,0,0"  # 设置 coverage_percent,total_lines,missing_lines                return 0  
            else  
                log_warning "[$module] 无法解析覆盖率信息"  
                COVERAGE_INFO="0,0,0"  # 设置 coverage_percent,total_lines,missing_lines                return 0  
            fi  
        fi        # 将覆盖率信息写入全局变量供主函数使用  
        COVERAGE_INFO="${coverage_percent},${total_lines:-0},${missing_lines:-0}"  
        if (( $(echo "$coverage_percent < $THRESHOLD" | bc -l) )); then  
            log_warning "[$module] 覆盖率 $coverage_percent% 低于阈值 $THRESHOLD%"  
            return 0  # 阈值不达标只是警告,不返回失败  
        else  
            log_success "[$module] 覆盖率 $coverage_percent% 达到阈值 $THRESHOLD%"  
            return 0  
        fi  
    else        log_error "[$module] diff-cover 分析失败"  
        log_error "命令输出: $diff_output"  
        return 1  
    fi  
}  
  
# ===== 显示使用帮助 =====show_help() {  
    cat << EOF  
合并版本:虚拟环境 + diff-cover 分析脚本  
  
用法:  
    $0 [选项] [环境变量...]  
  
选项:  
    --venv-dir DIR         指定虚拟环境目录 (默认: ./temp_venv)  
    --no-cleanup           执行后不清理虚拟环境,下次运行时复用  
    --help, -h             显示此帮助信息  
  
环境变量:  
    MODULES                要分析的模块列表 (默认: hinton-application,hinton-domain)  
    THRESHOLD              覆盖率阈值 (默认: 100)  
    OUTPUT_DIR             报告输出目录 (默认: ./reports)  
    SKIP_TESTS             跳过测试执行检查,使用现有报告 (默认: 0)  
    FORCE_ANALYSIS         强制分析 (默认: 0)  
    BASE_BRANCH            基准分支 (默认: 自动检测)  
    SKIP_BRANCH_CHECK      跳过远程分支比对 (默认: 0)  
    VENV_DIR               虚拟环境目录路径  
    CLEANUP_VENV           是否清理虚拟环境 (1=清理, 0=保留)  
  
示例:  
    $0                                    # 使用默认设置  
    $0 --venv-dir ./my_venv              # 指定虚拟环境目录  
    $0 --no-cleanup                      # 执行后保留虚拟环境  
    $0 MODULES=hinton-application,hinton-domain THRESHOLD=80   # 设置环境变量  
    $0 BASE_BRANCH=origin/main           # 手动指定基准分支  
    $0 SKIP_BRANCH_CHECK=1               # 跳过远程分支比对  
  
常见问题解决:  
    1. 找不到远程分支 origin/main:       - 检查网络连接: git fetch origin  
       - 手动指定远程分支: BASE_BRANCH=origin/main ./diff_cover_venv.sh  
       - 检查远程仓库配置: git remote -v  
    2. 未配置远程仓库:  
       - 添加远程仓库: git remote add origin <仓库URL>  
       - 手动指定远程分支: BASE_BRANCH=origin/main ./diff_cover_venv.sh  
    3. 网络连接问题:  
       - 检查网络连接和代理设置  
       - 手动指定远程分支: BASE_BRANCH=origin/main ./diff_cover_venv.sh  
       - 跳过远程分支比对: SKIP_BRANCH_CHECK=1 ./diff_cover_venv.sh  
    4. 跳过远程分支比对:  
       - 使用环境变量: SKIP_BRANCH_CHECK=1 ./diff_cover_venv.sh  
       - 将进行整体覆盖率分析,不进行分支比对  
  
注意:  
    - 脚本会自动创建临时虚拟环境  
    - 默认情况下执行完成后会自动清理虚拟环境  
    - 使用 --no-cleanup 可以保留虚拟环境,下次运行时复用(节省时间)  
    - 如果自动检测失败,可以手动指定 BASE_BRANCH 环境变量  
    - 使用 SKIP_BRANCH_CHECK=1 可以跳过远程分支比对,进行整体覆盖率分析  
    - 虚拟环境复用逻辑:保留时复用,清理时重建  
EOF  
}  
  
# ===== 解析命令行参数 =====parse_args() {  
    while [$# -gt 0](); do  
        case $1 in  
            --venv-dir)  
                VENV_DIR="$2"  
                shift 2  
                ;;  
            --no-cleanup)  
                CLEANUP_VENV=0  
                shift  
                ;;  
            --help|-h)  
                show_help  
                exit 0  
                ;;  
            *)  
                # 其他参数作为环境变量处理  
                break  
                ;;  
        esac    done}  
  
# ===== 主函数 =====main() {  
    log_info "开始基于虚拟环境的增量覆盖率分析..."  
    # 解析参数  
    parse_args "$@"  
    # 设置退出时清理  
    trap cleanup_venv EXIT  
      
    # 检查环境依赖  
    check_environment  
      
    # 创建临时虚拟环境  
    create_temp_venv  
      
    # 设置虚拟环境  
    setup_venv  
      
    # 检测模块和分支  
    detect_modules  
    detect_base_branch  
    check_branch_integrity  
      
    local success_count=0  
    local total_count=0  
    local total_covered_lines=0  
    local total_missing_lines=0  
    local total_all_lines=0  
    IFS=',' read -ra MODULE_LIST <<< "$MODULES"  
    for module in "${MODULE_LIST[@]}"; do  
        module=$(echo "$module" | xargs)  
        total_count=$((total_count + 1))  
          
        log_info "处理模块: $module"  
        # 检查覆盖率报告  
        local coverage_xml  
        if ! coverage_xml=$(check_coverage_report "$module"); then  
            log_error "模块 $module 处理失败"  
            continue  
        fi  
        # 转换Jacoco XML为Cobertura XML  
        local cobertura_xml="$module/target/jacoco/cobertura.xml"  
        # 确保目标目录存在  
        [! -d "$(dirname "$cobertura_xml")"]() && mkdir -p "$(dirname "$cobertura_xml")"  
        if ! convert_jacoco_to_cobertura "$coverage_xml" "$cobertura_xml"; then  
            log_error "模块 $module XML转换失败"  
            continue  
        fi  
        # 使用diff-cover分析并获取覆盖率信息  
        if analyze_diff_coverage "$module" "$cobertura_xml"; then  
            success_count=$((success_count + 1))  
              
            # 解析覆盖率信息: coverage_percent,total_lines,missing_lines  
            IFS=',' read -r coverage_percent total_lines missing_lines <<< "$COVERAGE_INFO"  
            # 累计统计信息  
            total_all_lines=$((total_all_lines + total_lines))  
            total_missing_lines=$((total_missing_lines + missing_lines))  
            total_covered_lines=$((total_covered_lines + (total_lines - missing_lines)))  
              
            log_info "[$module] 覆盖率: ${coverage_percent}% (${total_lines}行总计, ${missing_lines}行缺失)"  
        else  
            log_error "模块 $module diff-cover分析失败"  
        fi  
    done    # 总结  
    echo ""  
    echo "========================================"  
    echo "📊 分析完成"  
    echo "========================================"  
    echo "成功处理模块: $success_count/$total_count"  
    echo "报告目录: $OUTPUT_DIR"  
    # 计算合并的覆盖率  
    if [ $total_all_lines -gt 0 ]; then  
        local overall_coverage  
        overall_coverage=$(echo "scale=2; $total_covered_lines * 100 / $total_all_lines" | bc -l)  
          
        echo ""  
        echo "========================================"  
        echo "🎯 合并覆盖率统计"  
        echo "========================================"  
        echo "总代码行数: $total_all_lines"  
        echo "已覆盖行数: $total_covered_lines"  
        echo "缺失行数: $total_missing_lines"  
        echo "合并覆盖率: ${overall_coverage}%"  
        # 检查是否达到阈值  
        if (( $(echo "$overall_coverage < $THRESHOLD" | bc -l) )); then  
            log_warning "合并覆盖率 ${overall_coverage}% 低于阈值 ${THRESHOLD}%"  
        else  
            log_success "合并覆盖率 ${overall_coverage}% 达到阈值 ${THRESHOLD}%"  
        fi  
    else        log_warning "没有检测到需要覆盖的代码行"  
    fi  
    if [ $success_count -eq $total_count ]; then  
        log_success "所有模块分析完成"  
        exit 0  
    else  
        log_error "部分模块分析失败"  
        exit 1  
    fi  
}  
  
# ===== 脚本入口 =====main "$@"